#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QImage>
#include <QFileDialog>
#include <QDebug>

const int points = 21;
const int tipIds[] = {4, 8, 12, 16, 20};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::ShowResultImage(cv::Mat image)
{
    ui->label_result->clear();
    cv::Mat outImage;
    cv::cvtColor(image, outImage, cv::COLOR_BGR2RGB);
    QImage qImage = QImage((unsigned char*)(outImage.data), outImage.cols, outImage.rows, outImage.step, QImage::Format_RGB888);
    int imageWidth = qImage.width();
    int imageHeight = qImage.height();

    if(imageWidth > 640)
    {
        imageHeight = (640*10 / imageWidth) * imageHeight /10;
        imageWidth = 640;
    }

    if(imageHeight > 480)
    {
        imageWidth = (480*10 / imageHeight) * imageWidth /10;
        imageHeight = 480;
    }

    qImage = qImage.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    ui->label_result->setPixmap(QPixmap::fromImage(qImage));
}

void MainWindow::ShowSourceImage(cv::Mat image)
{
    ui->label_source->clear();
    cv::Mat outImage;
    cv::cvtColor(image, outImage, cv::COLOR_BGR2RGB);
    QImage qImage = QImage((unsigned char*)(outImage.data), outImage.cols, outImage.rows, outImage.step, QImage::Format_RGB888);
    int imageWidth = qImage.width();
    int imageHeight = qImage.height();

    if(imageWidth > 640)
    {
        imageHeight = (640*10 / imageWidth) * imageHeight /10;
        imageWidth = 640;
    }

    if(imageHeight > 480)
    {
        imageWidth = (480*10 / imageHeight) * imageWidth /10;
        imageHeight = 480;
    }

    qImage = qImage.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    ui->label_source->setPixmap(QPixmap::fromImage(qImage));
}

void MainWindow::OpenCamera()
{
    camera.open(0);
    camera.set(cv::CAP_PROP_EXPOSURE, -3.7);
}

void MainWindow::CloseCanera()
{
    camera.release();
}

void MainWindow::SendVideoFrame()
{
    if(camera.isOpened())
    {
        camera >> frame;
        ShowSourceImage(frame);
        emit sign_VideoFrame(frame);
    }
}

cv::Mat MainWindow::YCrCbOtsuDetect(cv::Mat &src)
{
    cv::Mat ycrcbImg;
    cv::cvtColor(src, ycrcbImg, cv::COLOR_BGR2YCrCb);
    cv::Mat detect;
    std::vector<cv::Mat> channels;
    cv::split(ycrcbImg, channels);
    cv::Mat outputMask = channels[1];
    cv::threshold(outputMask, outputMask, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    src.copyTo(detect, outputMask);
    return detect;
}

bool MainWindow::HandKeyPointsDetect(cv::Mat src, std::vector<cv::Point> &handKeyPoints)
{
    int width = src.cols;
    int height = src.rows;
    float ratio = width / (float) height;
    int modeHeight = 368;
    int modeWidth = int(ratio * modeHeight);

    std::string modelFile = "pose_deploy.prototxt";
    std::string modelWeight = "pose_iter_102000.caffemodel";

    cv::dnn::Net net = cv::dnn::readNetFromCaffe(modelFile, modelWeight);

    cv::Mat blob = cv::dnn::blobFromImage(src, 1.0 / 255, cv::Size(modeWidth, modeHeight), cv::Scalar(0, 0, 0));

    net.setInput(blob, "image");

    cv::Mat output = net.forward();
    int H = output.size[2];
    int W = output.size[3];

    for (int i = 0; i < points; i++)
    {
        cv::Mat probMap(H, W, CV_32F, output.ptr(0, i));
        cv::resize(probMap, probMap, cv::Size(width, height));

        cv::Point keyPoint;
        double calssProb;
        cv::minMaxLoc(probMap, nullptr, &calssProb, nullptr, &keyPoint);

        handKeyPoints[i] = keyPoint;
    }

    return true;
}

bool MainWindow::HandPoseRecognition(std::vector<cv::Point> &handKeyPoints, int &count)
{
    std::vector<int> fingers;
    if(handKeyPoints[tipIds[0]].x > handKeyPoints[tipIds[0] - 1].x)
    {
        fingers.push_back(1);
    }
    else
    {
        fingers.push_back(0);
    }

    for (int i = 1; i < 5; i++)
    {
        if(handKeyPoints[tipIds[0]].y > handKeyPoints[tipIds[0] - 2].y)
        {
            fingers.push_back(1);
        }
        else
        {
            fingers.push_back(0);
        }
    }

    for (int i = 0; i < fingers.size(); i++)
    {
        if(fingers[i] == 1)
        {
            count++;
        }
    }

    return true;
}

bool MainWindow::HandPoseShowResult(cv::Mat &src, std::vector<cv::Point> &handKeyPoints, int &count)
{
    for(int i = 0; i < points; i++)
    {
        cv::circle(src, handKeyPoints[i], 3, cv::Scalar(0, 0, 255), -1);
        cv::putText(src, std::to_string(i), handKeyPoints[i], cv::FONT_HERSHEY_COMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
    }

    cv::putText(src, std::to_string(count), cv::Point(20, 60), cv::FONT_HERSHEY_COMPLEX, 2, cv::Scalar(0, 0, 128), 3);
    return true;
}

void MainWindow::ContourDetection(cv::Mat image)
{
    cv::GaussianBlur(image, image, cv::Size(5, 5), 0, 0);
    dst = YCrCbOtsuDetect(image);
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
    cv::morphologyEx(dst, morp, cv::MORPH_OPEN, kernel);
    cv::Canny(morp, binary, 50, 200);
    std::vector<cv::Vec4i> hierachy;
    cv::findContours(binary, contours, hierachy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); //提取最外层轮廓
    area.clear();

    for(unsigned long long i = 0; i < contours.size(); ++i)
    {
        area.push_back(cv::contourArea(contours[i]));
    }

    size_t maxPosition = (size_t)(std::max_element(area.begin(), area.end()) - area.begin()); //求面积最大的轮廓
    std::vector<std::vector<cv::Point>> convex(1);

    contS = cv::contourArea(contours[maxPosition]);

    if(contS >= 20000)
    {
        cv::convexHull(contours[maxPosition], convex[0], true);
        cv::drawContours(image, convex, 0, cv::Scalar::all(255));
        convexS = cv::contourArea(convex[0]);
        areaK = convexS / contS; //计算面积比
        arcK = arcLength(contours[maxPosition], true) / arcLength(convex[0], true);//计算周长比
        cv::Rect rects = cv::boundingRect(contours[maxPosition]);//获得包围手势轮廓的矩形

        cv::RNG rngs = { 12345 };
        cv::Scalar colors = cv::Scalar(rngs.uniform(0, 255), rngs.uniform(0, 255), rngs.uniform(0, 255));
        rectangle(frame, rects, colors, 2);//画出包围手势轮廓的矩形

        if (areaK <= 1.2) putText(frame, "Rock", cv::Point(rects.x, rects.y), cv::FONT_HERSHEY_TRIPLEX, 0.8, cv::Scalar(255, 200, 200), 2, (30, 144, 255));
        else if (arcK < 1.8) putText(frame, "Scissors", cv::Point(rects.x, rects.y), cv::FONT_HERSHEY_TRIPLEX, 0.8, cv::Scalar(255, 200, 200), 2, (30, 144, 255));
        else putText(image, "Paper", cv::Point(rects.x, rects.y), cv::FONT_HERSHEY_TRIPLEX, 0.8, cv::Scalar(255, 200, 200), 2, (30, 144, 255));
    }
    ShowResultImage(image);
}

void MainWindow::KeypointDetection(cv::Mat image)
{
    std::vector<cv::Point> HandKeyPoints(points);
    HandKeyPointsDetect(image, HandKeyPoints);

    int count = 0;
    HandPoseRecognition(HandKeyPoints, count);
    HandPoseShowResult(image, HandKeyPoints, count);
    ShowResultImage(image);
}

void MainWindow::on_action_start_camera_triggered()
{
    OpenCamera();
    connect(&timer, &QTimer::timeout, this, &MainWindow::SendVideoFrame);
    timer.start(40);
}

void MainWindow::on_action_end_camera_triggered()
{
    CloseCanera();
    timer.stop();
    disconnect(&timer, &QTimer::timeout, this, &MainWindow::SendVideoFrame);
}

void MainWindow::on_action_key_triggered()
{
    on_action_end_camera_triggered();
    disconnect(this, &MainWindow::sign_VideoFrame, this, &MainWindow::ContourDetection);
    connect(this, &MainWindow::sign_VideoFrame, this, &MainWindow::KeypointDetection);
}

void MainWindow::on_action_con_triggered()
{
    disconnect(this, &MainWindow::sign_VideoFrame, this, &MainWindow::KeypointDetection);
    connect(this, &MainWindow::sign_VideoFrame, this, &MainWindow::ContourDetection);
}

void MainWindow::on_action_image_triggered()
{
    QString imagePath = QFileDialog::getOpenFileName(this,"选择图片","./","*png *jpg *jpeg *webp");
    QImage image;
    if(image.load(imagePath))
        qDebug() << "导入图片成功" << imagePath;
    std::string sourcePath = QDir::toNativeSeparators(imagePath).toStdString();
    qDebug() << "图片路径:" << QDir::toNativeSeparators(imagePath);
    qDebug() << " sourcePath:" << QString::fromStdString(sourcePath);
    cv::Mat cv_image = cv::imread(sourcePath);
    if(cv_image.empty())
    {
        qDebug() << "image is empty." << sourcePath.c_str();
        return;
    }
    ShowSourceImage(cv_image);
    emit sign_VideoFrame(cv_image);
}
